#include "CScnSolid.h"
#include <iostream>

using namespace std;
using namespace irr;
using namespace video;

CScnSolid::~CScnSolid()
{
    //pointers must be set to zero initially otherwise errors
    if (rawcells)
    {
        for (u32 i=0; i < n_cells; i++)
        {
            if (rawcells[i].nodesidxs)
                delete [] rawcells[i].nodesidxs;

            if (rawcells[i].portals)
            {
                for (s32 j=0; j < rawcells[i].n_portals; j++)
                    if (rawcells[i].portals[j].verts)
                        delete [] rawcells[i].portals[j].verts;

                delete [] rawcells[i].portals;
            }
        }
        delete [] rawcells;
    }

    if (uvidxs)
        delete [] uvidxs;

    if (vertidxs)
        delete [] vertidxs;

    if (uvpos)
        delete [] uvpos;

    if (verts)
        delete [] verts;

    if (planes)
        delete [] planes;

    if (tree)
        delete tree;

    if (surfsad)
        delete [] surfsad;

    if (surfs)
    {
        for (u32 i=0;i < n_surfs ;i++)
            if (surfs[i].more==1)
                delete [] surfs[i].shading;

        delete [] surfs;
    }

   if (uvpos_caller)
      delete [] uvpos_caller;

   if (uvidxs_caller)
      delete [] uvidxs_caller;

}

int CScnSolid::loadSolid(std::ifstream * file)
{

    file->seekg(offset);
    SAY("offset: %u\n",offset);
    n_unk1=read_u32(file);
    n_verts=read_u32(file);
    n_uvpos=read_u32(file);
    n_vertidxs=read_u32(file);
    n_planes=read_u32(file);
    n_nodes=read_u32(file);
    n_surfs=read_u32(file);
    n_cells=read_u32(file);
    n_unk2=read_u32(file);

    length=read_u32(file);

    //must be in this order
    loadSurfs(file);
    loadNodes(file);
    loadPlanes(file);
    loadVerts(file);
    loadUVPos(file);
    loadVertIdxs(file);
    loadUVIdxs(file);

    loadUnk(file);
    loadCells(file);

    s32 n_texs = calcUniqueTexturesNames(file);
    SAY("\n\t%u unique textures\n",n_texs);

    if (n_texs != textures.size())
        error(true,"loadSolid: Number of unique textures and texture array size doesn't match");

    buildBackTree();

    return 1;
}

int CScnSolid::loadCells(std::ifstream * file)
{
    SAY("\tGetting Cells...");
    rawcells = new scnRawCell_t[n_cells];

    for (u32 i=0; i < n_cells; i++)
    {
        rawcells[i].nodesidxs=0;
        rawcells[i].portals=0;

        SAY("\n\t\tcell[%u]: ",i);
        read_generic(rawcells[i].name,file,32);
        SAY("%s ", rawcells[i].name);
        rawcells[i].n_nodesidxs=read_s32(file);
        SAY("%d ", rawcells[i].n_nodesidxs);
        rawcells[i].n_portals=read_s32(file);
        SAY("%d ", rawcells[i].n_portals);
        rawcells[i].n3=read_s32(file);
        SAY("%d ", rawcells[i].n3);
        read_generic(rawcells[i].skyname,file,32);
        SAY("%s ", rawcells[i].skyname);

        rawcells[i].nodesidxs = new u16[rawcells[i].n_nodesidxs];
        read_generic(rawcells[i].nodesidxs,file,2 * rawcells[i].n_nodesidxs);

        rawcells[i].portals = new scnPortal_t[rawcells[i].n_portals];
        SAY("Reading %u portals... ", rawcells[i].n_portals);
        for (s32 j=0; j < rawcells[i].n_portals; j++)
        {
            rawcells[i].portals[j].verts=0;
            loadPortal(&(rawcells[i].portals[j]),file);
        }
        SAY("done.");

        loadCellData(file);
    }


   SAY("done.");

    return n_cells;
}
//read bboxes and surfaces
int CScnSolid::loadCellData(std::ifstream * file)
{
    u16 nn;
    s32 n=0;
    u16 n_surfs;

    //just skip for now
    while(n>-1)
    {
        file->seekg(sizeof(point3f)*2,std::ios::cur);

        nn=read_u16(file);
        n += nn;

        n_surfs=read_u16(file);
        file->seekg(2*n_surfs,ios::cur);

        n--;

    }

    return 0;
}

int CScnSolid::loadPortal(scnPortal_t * portal, std::ifstream * file)
{
    //SAY("\n\t\t\tReading portal...");
    read_generic(portal->name,file,32);
    //SAY("(%i)", strlen(portal->name));
    //SAY("%s ", portal->name);

    portal->nextcell=read_s32(file);
    //SAY("%u ",portal->nextcell);
    read_generic(&(portal->plane),file,sizeof(scnPlane_t));
    portal->unk=read_f32(file);
    //SAY("(%.1f %.1f %.1f %.1f %.1f)",portal->plane.a, portal->plane.b, portal->plane.c, portal->plane.d, portal->unk);

    portal->n_verts=read_s32(file);
    read_generic(portal->bb_verts,file,sizeof(point3f)*2);

    portal->verts = new point3f[portal->n_verts];
    read_generic(portal->verts, file, sizeof(point3f) * portal->n_verts);

    //SAY("done reading portal. ");
    return 1;
}

int CScnSolid::loadUnk(std::ifstream * file)
{
    SAY("\tSkipping Unk lump...");

    //About this lump only know it's 9 floats per surface
    file->seekg(36*n_surfs,ios::cur);

    SAY("done.\n");
    return 0;
}


int CScnSolid::calcUniqueTexturesNames(std::ifstream * file)
{
    int ret=0;
    bool found=false;

    for (u32 i=0; i < n_surfs; i++)
    {
        string texname(surfs[i].texture);
        found=false;
        for (int j=0; i < textures.size(); i++)
        {
            if (textures[j]==texname)
            {
                found=true;
                break;
            }
        }
        if (!found)
        {
            textures.push_back(texname);
            ret++;
        }
    }
    return ret;

}

int CScnSolid::loadUVIdxs(std::ifstream * file)
{
    u32 nvui = n_vertidxs;
    SAY("\tGetting UV coordinates indices...");

    uvidxs = new u32[nvui];   //allocate   n_uvidxs=n_vertidxs
    read_generic(uvidxs,file,sizeof(u32)*nvui);
    SAY("%u done.\n",nvui);

    return nvui;

}

void CScnSolid::buildBackTree()
{
    u32 n_uvidxs = n_vertidxs;

    uvpos_caller = new core::array<u32>[n_uvpos];
    //build backtree
    for (u32 i=0; i< n_uvidxs ;i++)
        uvpos_caller[uvidxs[i]].push_back(i);

    uvidxs_caller = new core::array<u32>[n_uvidxs];
    for (u32 i=0; i< n_surfs; i++)
        for (u32 j=0; j< surfs[i].vertidxlen; j++)
            uvidxs_caller[surfs[i].vertidxstart + j].push_back(i);

    // TESTS to make sure this is right ----------------------------
    /*bool bPassed = true;

    for (u32 i=0; i<n_uvpos; i++)
    {
        for (u32 j=0; j<uvpos_caller[i].size();j++)
        {
            point2f uvposa = uvpos[uvidxs[uvpos_caller[i][j]]];
            if ((uvposa.u != uvpos[i].u) || (uvposa.v != uvpos[i].v))
            {
                bPassed = false;
                break;
            }
        }
    }

    for (u32 i=0; i<n_uvidxs; i++)
    {
        for (u32 j=0; j<uvidxs_caller[i].size();j++)
        {
            scnSurf_t * surfi = &surfs[uvidxs_caller[i][j]];
            bool bContains=false;
            for (u32 k=0; k<surfi->vertidxlen; k++)
            {
                if ((surfi->vertidxstart + k) == i)
                {
                    bContains = true;
                    break;
                }
            }
            if (!bContains)
            {
                bPassed = false;
                break;
            }
        }
    }

    if (!bPassed) error(true, "buildBackTree() failed, tests not passed");
    */

}


int CScnSolid::loadVertIdxs(std::ifstream * file)
{
    SAY("\tGetting Vertex indices...");

    vertidxs = new u32[n_vertidxs];   //allocate
    read_generic(vertidxs,file,sizeof(u32)*n_vertidxs); //read all, should work

    SAY("%u done.\n",n_vertidxs);

    return n_vertidxs;
}

int CScnSolid::loadUVPos(std::ifstream * file)
{
    SAY("\tGetting UV coordinates...");

    uvpos = new point2f[n_uvpos];   //allocate
    uvposad=file->tellg();
    read_generic(uvpos,file,sizeof(point2f)*n_uvpos); //read all, should work

    SAY("%u done.\n",n_uvpos);

    return n_uvpos;
}

int CScnSolid::loadVerts(std::ifstream * file)
{
    SAY("\tGetting Vertices...");
    verts = new point3f[n_verts];   //allocate
    read_generic(verts,file,sizeof(point3f)*n_verts); //read all, should work
    SAY("%u done.\n",n_verts);

    return n_verts;

}

int CScnSolid::loadPlanes(std::ifstream * file)
{
    SAY("\tGetting planes...");
    planes = new scnPlane_t[n_planes];   //allocate

    tree->planes = planes; //IMPORTANT
                            //TODO: make better

    read_generic(planes,file,sizeof(scnPlane_t)*n_planes); //read all, should work
    SAY("%u done.\n",n_planes);

    return n_planes;

}

int CScnSolid::loadNodes(std::ifstream * file)
{
    SAY("\tReading nodes...");
    //file->seek(n[N_NODES]*16,true);

    tree = new CScnBSPTree(n_nodes);

    if (tree->loadNodes(file) == -1)
        error(true,"loadNodes: Error reading nodes from solid,n_nodes < 1");

    SAY("done.\n");

    return 0;

}

int CScnSolid::loadSurfs(std::ifstream * file)
{
    SAY("\tGetting Surfaces...");

    surfs = new scnSurf_t[n_surfs];
    surfsad = new int[n_surfs];
    int i;
    for (i=0;i < n_surfs ;i++)
	{
		surfsad[i]=file->tellg();

		read_generic(&surfs[i],file,72);   //read the usual 72 first bytes

		if (surfs[i].more==1)  //means there are more bytes - the shading or smoothing or whatever we call it
        {
            surfs[i].shading = new u8[4*surfs[i].vertidxlen];     //allocate
            read_generic(surfs[i].shading,file,4*surfs[i].vertidxlen);
            //REMEMBER: because shading is initially set to  a random value, we must
            //make sure we only try to draw shading only when more is set to !0 or
            //make constructor to set initial value 0;
        }
        else if (surfs[i].more !=0)
			error(true,"CScnSolid: loadSurfs - Unexpected surface[%u].more value - expected 0 or 1",i);
	}
    SAY("%u done.\n",i);
    return i;
}

//default constructor
CScnSolid::CScnSolid()
{
    offset=0;
    length=0;
    //TODO: do something
    textures.clear();

	//setting pointers to zero
	rawcells=0;
	uvidxs=0;
	vertidxs=0;
	uvpos=0;
	verts=0;
	planes=0;
	tree=0;
	surfs=0;
	surfsad=0;
	uvposad=0;

}
#ifdef __IRRLICHT_H_INCLUDED__
//return array of surface vertices with alpha, shading and uv information
//irr_specific
core::array<S3DVertex> CScnSolid::calcSurfVertices(u32 surfidx)
{
    core::array<S3DVertex> v;
    scnSurf_t * surfi = &surfs[surfidx];

    S3DVertex tVert;

    u8 * ps= surfi->shading;

    for (u32 i=0; i < surfi->vertidxlen; i++)
    {
        tVert = getVertice(vertidxs[surfi->vertidxstart+i]);

        tVert.TCoords.X=uvpos[uvidxs[surfi->vertidxstart+i]].u;
        tVert.TCoords.Y=uvpos[uvidxs[surfi->vertidxstart+i]].v;

        u32 a = surfi->alpha;

        if (a!=255 && GLOBALALPHA> 0) a = GLOBALALPHA; //override with l
        //if (a!=255) //removed this
        tVert.Color=video::SColor(a,a,a,a); //fixed (alpha should be set on all colors. Why?

        if (surfi->more)
        {
            //if there is shading, we find that ps[3] always equals surfi->alpha
            //we could use this to make fun things like make vertices of the same surface have different transparencies
            tVert.Color = video::SColor(ps[3],ps[0],ps[1],ps[2]);
            ps+=4;
        }
        v.push_back(tVert);
    }
    //debug
    //check if there are 3 colinear vertices
    /*

    a = v[1].Pos - v[0].Pos;
    b = v[2].Pos - v[1].Pos;
    cp = a.crossProduct(b);
    if (cp == zero)
        SAY("first 3 verts are colinear");

    a = v[surfi->vertidxlen-3].Pos - v[surfi->vertidxlen-2].Pos;
    b = v[surfi->vertidxlen-2].Pos - v[surfi->vertidxlen-1].Pos;
    cp = a.crossProduct(b);
    if (cp == zero)
        SAY("last 3 verts are colinear");*/

    core::vector3df a,b,cp,zero(0,0,0);
    for (u32 j=0; j<surfi->vertidxlen; j++)
    {
        u32 idxs[3];
        idxs[0] = j;
        idxs[1] = j+1;
        idxs[2] = j+2;
        if (j == surfi->vertidxlen-2)
        {
            idxs[2] = 0;
        }
        else if (j == surfi->vertidxlen-1)
        {
            idxs[1] = 0;
            idxs[2] = 1;
        }

        a = v[idxs[2]].Pos-v[idxs[0]].Pos;
        b = v[idxs[1]].Pos-v[idxs[0]].Pos;
        cp = a.crossProduct(b);
        //TODO: THIS
        //if (cp == zero)
            //SAY("Surf %u: Verts %d %d %d are colinear\n",surfidx,idxs[0],idxs[1],idxs[2]);
    }


    return v;
}
#endif;

#ifdef __IRRLICHT_H_INCLUDED__
irr::core::array<u16> CScnSolid::calcSurfIndices(u32 surfidx)
{
    core::array<u16> idxs;
    scnSurf_t * surfi = &surfs[surfidx];

    //we pass the vertices always in the order 0,1,2; 2,3,0; 3,4,0; ...
    //                  (swat and irrlicht are counter-clockwise)

    idxs.push_back(0);
    idxs.push_back(1);
    idxs.push_back(2);

    //each surface is a new meshbuffer - because they may have different textures
    //draw mesh like a triangle fan - first three vertices define a triangle,
    //from then, each new one defines a tringle with the last and the origin (0)


    for (u16 j=3; j<surfi->vertidxlen ;j++)
    {
        idxs.push_back(j-1);
        idxs.push_back(j);
        idxs.push_back(0);
    }
    return idxs;

}

#else
vector<u16> CScnSolid::calcSurfIndices(u32 surfidx)
{
    vector<u16> idxs;
    scnSurf_t * surfi = &surfs[surfidx];

    idxs.push_back(0);
    idxs.push_back(1);
    idxs.push_back(2);

    //each surface is a new meshbuffer - because they may have different textures
    //draw mesh like a triangle fan - first three vertices define a triangle,
    //from then, each new one defines a tringle with the last and the origin (0)

    for (u16 j=3; j<surfi->vertidxlen ;j++)
    {
        idxs.push_back(j-1);
        idxs.push_back(j);
        idxs.push_back(0);
    }
    return idxs;

}
#endif
